package com.finance.cooperate.feature.service.impl;

import com.alibaba.fastjson.JSON;
import com.finance.cooperate.common.constant.CodeMsg;
import com.finance.cooperate.common.constant.Constant;
import com.finance.cooperate.common.exception.DBConnectException;
import com.finance.cooperate.common.pojo.RestResult;
import com.finance.cooperate.common.properties.CooperateProperties;
import com.finance.cooperate.common.utils.ClassificationModel;
import com.finance.cooperate.common.utils.ProbabilityUtils;
import com.finance.cooperate.common.utils.StringUtil;
import com.finance.cooperate.dao.entity.secondary.DiversionInfo;
import com.finance.cooperate.dao.entity.secondary.ModelFeature;
import com.finance.cooperate.dao.entity.secondary.ModelInfo;
import com.finance.cooperate.dao.entity.secondary.ModelResult;
import com.finance.cooperate.dao.local.cache.PmmlPool;
import com.finance.cooperate.dao.local.scope.CooperateRequestScope;
import com.finance.cooperate.dao.repository.secondary.DiversionInfoRepository;
import com.finance.cooperate.dao.repository.secondary.ModelFeatureRepository;
import com.finance.cooperate.dao.repository.secondary.ModelInfoRepository;
import com.finance.cooperate.dao.repository.secondary.ModelResultRepository;
import com.finance.cooperate.feature.builder.*;
import com.finance.cooperate.feature.core.feature.FeatureDefinition;
import com.finance.cooperate.feature.feature.ModelFeatureSet;
import com.finance.cooperate.feature.service.ModelService;
import com.finance.cooperate.strategy.common.params.StrategyParams;
import com.finance.cooperate.strategy.service.StrategyInfoService;
import lombok.extern.slf4j.Slf4j;
import org.dmg.pmml.FieldName;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.text.DecimalFormat;
import java.util.*;

@Slf4j
@Service
public class ModelServiceImpl implements ModelService {

    @Autowired
    private DiversionInfoRepository diversionInfoRepository;
    @Autowired
    private ModelInfoRepository modelInfoRepository;
    @Autowired
    private ModelResultRepository modelResultRepository;
    @Autowired
    private ModelFeatureRepository modelFeatureRepository;

    @Autowired
    private StrategyInfoService strategyInfoService;

    @Autowired
    private CooperateProperties cooperateProperties;

    @Autowired
    private CooperateRequestScope requestScope;


    @Override
    public RestResult execution(String uid, Long loanNum) {

        Double score = 1.0;

        DiversionInfo diversionInfo = null;

        Map<String, String> featureMap = new HashMap<>();

        Set<String> featureSet = new HashSet<>();

        String applyId = requestScope.getApplyId();

        String project = cooperateProperties.getEnvironment().getProject();

        // 代码中判断走那个分离器
        try {
            if (loanNum == 1) {
                diversionInfo = diversionInfoRepository.findByNameAndProject(Constant.DIVERSION_NAME_FIRST_LOAN_DIVERSION, project);

                if (diversionInfo != null) {
                    featureMap = (Map<String, String>) new FirstLoanFeatureProcess().run(uid, new FirstLoanReaderBuilder()).getData();
                    featureSet = ModelFeatureSet.firstLoanModelResult;
                }
            } else if (loanNum > 1 && loanNum < 4) {

                diversionInfo = diversionInfoRepository.findByNameAndProject(Constant.DIVERSION_NAME_SECOND_LOAN_DIVERSION, project);

                if (diversionInfo != null) {
                    featureMap = (Map<String, String>) new SecondLoanFeatureProcess().run(uid, new SecondLoanReaderBuilder()).getData();
                    featureSet = ModelFeatureSet.secondLoanModelResult;
                }

            } else if (loanNum >= 4) {
                diversionInfo = diversionInfoRepository.findByNameAndProject(Constant.DIVERSION_NAME_THIRD_LOAN_DIVERSION, project);

                if (diversionInfo != null) {
                    featureMap = (Map<String, String>) new ThirdLoanFeatureProcess().run(uid, new ThirdLoanReaderBuilder()).getData();
                    featureSet = ModelFeatureSet.thirdLoanModelResult;
                }
            }
        } catch (DBConnectException e) {
            return RestResult.build(CodeMsg.DB_TIMEOUT);
        }


        // 将公用特征加进去
        featureSet.addAll(ModelFeatureSet.observedFeatureResult);

        // 找到模型引擎后，执行模型
        if (diversionInfo != null) {

            Long diversionId = diversionInfo.getId();
            List<ModelInfo> modelInfos = modelInfoRepository.getDiversionId(diversionId);

            if (modelInfos.size() > 0) {

                double[] weightList = new double[modelInfos.size()];

                for (int i = 0; i < modelInfos.size(); i++) {
                    weightList[i] = modelInfos.get(i).getWeight();
                }

                int index = ProbabilityUtils.shuntSelection(weightList, Long.valueOf(uid.hashCode()) * diversionId);

                // 选取模型
                ModelInfo modelInfo = modelInfos.get(index);

                ClassificationModel model = PmmlPool.getInstance().getModel(modelInfo.getFile());

                Double predictProba = this.predictProba(featureMap, model);

                // 分数阈值处理
                score = this.getScore(modelInfo.getThreshold(), predictProba);

                // 保存模型分数特征等(善后处理)
                Map<String, String> keyAndValue = new HashMap<>();

                for (String key : featureSet) {

                    // 上面处理的不靠谱，不通过的模型还是没处理
                    // 23.7.12 为了配合特征监控，部分特征必须保留，但是可能取不到线上值
                    String value = featureMap.getOrDefault(key, null);

                    if (StringUtil.isEmptyOrNvl(value)) {
                        boolean oneHot = FeatureDefinition.isOneHot(key);
                        if (oneHot) {
                            value = String.valueOf(Constant.IS_NO_HIT);
                        } else {
                            value = String.valueOf(Constant.MISSING_VALUE);
                        }
                    }

                    keyAndValue.put(key, value);
                }

                Double modelScore = new Double(score);

                // 执行后置策略
                try {

                    StrategyParams params = new StrategyParams();
                    params.setUid(uid);
                    params.setApplyId(applyId);
                    params.setLoanNum(loanNum);
                    params.setModelId(modelInfo.getId());
                    params.setScore(score);
                    params.setFeatureMap(featureMap);

                    RestResult strategyResult = strategyInfoService.strategy(params);

                    if (strategyResult.isOK()) {
                        score = (Double) strategyResult.getData();
                    }

                } catch (Exception e) {
                    log.error("策略执行失败: ", e);
                }


                // 模型保存调整在这里，因为 score 还可能被策略改
                ModelResult modelResult = new ModelResult();
                modelResult.setUid(uid);
                modelResult.setResult(String.valueOf(predictProba));

                String strScore = String.valueOf(score);
                if (strScore.length() > 7) {
                    strScore = strScore.substring(0, 7);
                }

                modelResult.setScore(strScore);
                modelResult.setModelScore(modelScore.floatValue());
                modelResult.setLoanNum(loanNum);
                modelResult.setModelId(modelInfo.getId());
                modelResult.setProject(project);

                ModelResult mr = modelResultRepository.save(modelResult);

                // 特征保存
                ModelFeature feature = new ModelFeature();
                feature.setId(mr.getId());
                feature.setFeatureValue(JSON.toJSONString(keyAndValue));
                modelFeatureRepository.save(feature);

            }

        }


        // 拼装结果
        Map<String, Object> map = new HashMap<>();

        map.put("score", score);

        map.put("apply_id", applyId);

        return RestResult.ok(map);

    }


    /**
     * @Author shen
     * @Description 根据预测概率找到对应得模型分
     * @Date 11:20 2022/10/8
     * @Param [model, predictProba]
     * @return java.lang.Double
     **/
    private Double getScore(String threshold, Double predictProba) {

        Double score = 1.0;

        threshold = threshold.replace(" ", "")
                .replace("\n", "")
                .replace("\t", "");

        if (!StringUtil.isEmptyOrNvl(threshold)) {


            String[] thresholdList = threshold.split(",");

            if (thresholdList.length > 1) {
                // 说明是分段
                int indexThreshold = 0;

                for (String t: thresholdList) {

                    Double td = Double.valueOf(t);

                    if (predictProba != null && predictProba < td) {
                        break;
                    }

                    indexThreshold ++;

                }

                // 计算分数
                float v = (1 - (float)indexThreshold / thresholdList.length) * 100;

                DecimalFormat df = new DecimalFormat("0.00");

                score = Double.valueOf(df.format(v));


            } else { // 直接当阈值

                if (predictProba != null && predictProba < Double.valueOf(threshold)) {
                    // 分数目前写死
                    score = 90.0;
                }

            }

        }


        return score;

    }


    /**
     * @Author shen
     * @Description 获取预测值
     * @Date 11:16 2022/10/8
     * @Param [featureMap, model]
     * @return java.lang.Double
     **/
    private Double predictProba(Map<String, String> featureMap, ClassificationModel model) {

        List<String> featureNames = model.getFeatureNames();
        Map<FieldName, Number> waitPreSample = new HashMap<>();


        for (String name : featureNames) {

            // 获取特征值：
            Double value = -999.0;

            String v = featureMap.get(name);
            if (StringUtil.isEmptyOrNvl(v)) {
                boolean oneHot = FeatureDefinition.isOneHot(name);
                if (oneHot) {
                    value = (double) Constant.IS_NO_HIT;
                } else {
                    value = (double) Constant.MISSING_VALUE;
                }
                // 设置完成放到map中
                featureMap.put(name, StringUtil.obj2Feature(value));
            } else {
                value = Double.parseDouble(v);
            }

            waitPreSample.put(FieldName.create(name), value);

        }

        return model.predictProba(waitPreSample);

    }
}
